/*
 * midi.h
 *
 *  Created on: 27-Jun-2009
 *      Author: nrqm
 */

#ifndef MIDI_H_
#define MIDI_H_

#include <stdlib.h>
#include <avr/io.h>
#include "ThreeByteRing.h"

/**
 * The commands defined explicitly in the MIDI protocol.
 * There are a bunch of other status bytes (system common and system real time messages) that are not
 * covered here.  Currently this program only handles the NOTE_ON and PITCH_BEND messages.
 */
typedef enum _mcmd
{
	NOTE_OFF,				/// turn a note off.
	NOTE_ON,				/// turn a note on.
	AFTERTOUCH,				/// apply aftertouch to a note (e.g. changes in velocity after it is turned on).
	CONTROLLER,				/// set the value for some controller (switch, knob, etc.).
	PATCH_CHANGE,			/// change the program/patch that the device is running (e.g. change instrument).
	CHANNEL_PRESSURE,		/// similar to aftertouch, but changes velocity for all notes on channel.
	PITCH_BEND,				/// change a note's pitch with fine resolution, e.g. in response to a pitch wheel.
} MIDI_COMMAND;

/// The total number of MIDI commands.
static const uint8_t num_commands = 7;
/// The maximum number of objects that can listen for a given MIDI command.
static const uint8_t max_listeners = 3;

/**
 * The parameters to a note on command.  See MidiNoteOnListener::DoNoteOn.
 */
typedef struct _no
{
	uint8_t note;
	uint8_t velocity;
} note_on_t;

/**
 * The parameter to a pitch bend command.  See MidiPitchBendListener::DoPitchBend.
 */
typedef struct _pb
{
	uint16_t pitch;
} pitch_bend_t;

/**
 * General 2-byte parameter to a MIDI note on or pitch bend command.
 */
typedef union _param
{
	note_on_t note_on;
	pitch_bend_t pitch_bend;
} midi_param_t;

class MidiListener;

/**
 * A class to handle a subset of the MIDI protocol.  To use this class, register objects derived from the
 * listener classes defined below for the note on (MidiNoteOnListener) and pitch bend (MidiPitchBendListener)
 * commands.  Then repeatedly call Midi::CheckMidi to check for commands and pass them on to the listeners.
 */
class Midi
{
private:
	/// Array of command listeners; has room for three listeners for each of the commands.
	MidiListener* listeners[num_commands][max_listeners];
	/// The last command byte that was received, masked with 0xF0 (command nibble).
	uint8_t last_command;
	/// The last command byte that was received, masked with 0x0F (channel nibble).
	uint8_t last_channel;
	/// A 3-byte ring buffer to store the last three bytes received over serial.
	ThreeByteRing buffer;
	/// Constructor; do not instantiate this class, use GetInstance instead.
	Midi();
	/**
	 * If there are precisely two data bytes in the ring buffer, then this returns 1; otherwise, it returns 0.
	 * A MIDI data byte is an 8-bit value with the MSb cleared (in contrast, MIDI command bytes have the MSb set).
	 * This is useful because MIDI commands are often sent without repeating the command byte.  For example,
	 * if a controller sends the PITCH_BEND message several times in a row with no other messages interrupting
	 * the stream, it might send the PITCH_BEND command byte once followed by several pairs of data bytes.
	 * Controllers do this to reduce the amount of data that need to be transmitted.  Some controllers send the
	 * command byte explicitly in every message.
	 *
	 * If this function returns 1, then the buffer contains a new command message with the command byte left
	 * implied by the command byte that was received most recently.  Some commands don't have two-byte
	 * arguments, so this isn't a general method, but both the commands that this class handles (note on and pitch
	 * bend) have two-byte arguments.
	 *
	 * If the ring buffer has size 2, this function empties the buffer temporarily, but it will return the buffer
	 * to its original apparent state before returning (the buffer's internal state may be changed).
	 */
	uint8_t BufferContainsImplicitCommand();

public:
	/**
	 * Retrieve the singleton instance of the Midi class.
	 */
	static Midi* GetInstance();
	/**
	 * Register a command listener object.  The object's appropriate method will be called when the
	 * Midi class receives the object's corresponding command.  For example, if a note on message is
	 * received, then the note on listeners' DoNoteOn method is called (if at least one
	 * MidiNoteOnListener object has been registered).  Up to max_listeners listeners of each command
	 * type can be registered.
	 */
	void RegisterListener(MidiListener* listener);
	/**
	 * Build the command packet from the argument bytes and pass it on to the command's listeners
	 * \param byte1 The first argument byte that was received.
	 * \param byte2 The second argument byte that was received.
	 */
	void NotifyListeners(uint8_t byte1, uint8_t byte2);
	/**
	 * Read in a single byte from the serial buffer and store it.  Once a 3-byte MIDI message has been
	 * completely received, pass it on to the appropriate listener.  This should be called from the
	 * program's main loop.
	 */
	uint8_t CheckMidi();
};

/**
 * Abstract class representing an object that can listen for a MIDI command.  A class derived from
 * MidiListener can be registered with the Midi object as a command listener using RegisterListener.
 */
class MidiListener
{
private:
	MIDI_COMMAND command;
protected:
	/**
	 * Constructor.
	 */
	MidiListener(MIDI_COMMAND cmd);
public:
	/**
	 * Get the type of MIDI command that this object is listening to.
	 */
	MIDI_COMMAND GetCommandType();
	/**
	 * Pass on a command to the listener.  This should only be called by the Midi class in response to
	 * a 3-byte command message being received.  This function will call the appropriate function in
	 * the concrete listener class (e.g. DoNoteOn, DoPitchBend).
	 */
	void ProcessCommand(midi_param_t* param);
};

/**
 * Abstract class representing an object that can listen for MIDI note on commands.  A class derived
 * from MidiNoteOnListener can be registered with the Midi object as a listener for note on commands.
 */
class MidiNoteOnListener : public MidiListener
{
protected:
	/**
	 * Constructor.
	 */
	MidiNoteOnListener();
public:
	/**
	 * This method is called by the Midi class when it receives a note on message.
	 * \param note The note byte that was sent as an argument with the MIDI note on message (0x00 - 0x7F).
	 * \param velocity The velocity byte that was sent as an argument with the MIDI note on message (0x00 - 0x7F).
	 */
	virtual void DoNoteOn(uint8_t note, uint8_t velocity) = 0;
};

/**
 * Abstract class representing an object that can listen for MIDI pitch bend commands.  A class derived
 * from MidiPitchBendListener can be registered with the Midi object as a listener for pitch bend commands.
 */
class MidiPitchBendListener : public MidiListener
{
protected:
	/**
	 * Constructor.
	 */
	MidiPitchBendListener();
public:
	/**
	 * This method is called by the Midi class when it receives a pitch bend message.
	 * \param pitch The pitch bend value that was sent as an argument with the MIDI pitch bend message
	 * 		(0x0000 - 0x4000).
	 */
	virtual void DoPitchBend(int16_t pitch) = 0;
};

#endif
